package com.xuecheng.user.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xuecheng.base.constant.RoleIdConstant;
import com.xuecheng.base.constant.UserTypeConstant;
import com.xuecheng.user.enums.AuthType;
import com.xuecheng.user.mapper.XcUserMapper;
import com.xuecheng.user.mapper.XcUserRoleMapper;
import com.xuecheng.user.model.dto.AuthParamsDTO;
import com.xuecheng.user.model.dto.XcUserExt;
import com.xuecheng.user.model.po.XcUser;
import com.xuecheng.user.model.po.XcUserRole;
import com.xuecheng.user.service.AuthService;
import com.xuecheng.user.service.WxAuthService;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;

import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.UUID;

@Slf4j
@Service("wxAuthService")
@RequiredArgsConstructor
public class WxAuthServiceImpl implements AuthService, WxAuthService {

    private final XcUserMapper xcUserMapper;

    private final XcUserRoleMapper xcUserRoleMapper;

    private final RestTemplate restTemplate;

    private final PasswordEncoder passwordEncoder;

    @Value("${xuecheng.weixin.appid}")
    String appid;

    @Value("${xuecheng.weixin.secret}")
    String secret;

    /**
     * 认证方法
     *
     * @param authParamsDTO 认证参数
     * @return 用户信息
     */
    @Override
    public XcUserExt auth(AuthParamsDTO authParamsDTO) {
        // 1. 根据用户名查询用户信息
        String username = authParamsDTO.getUsername();
        XcUser xcUser = xcUserMapper.selectOne(new LambdaQueryWrapper<XcUser>().eq(XcUser::getUsername, username));
        // 2. 判断用户是否存在
        if (xcUser == null) throw new RuntimeException("用户不存在");
        // 3. 组装用户信息
        XcUserExt xcUserExt = new XcUserExt();
        BeanUtils.copyProperties(xcUser, xcUserExt);
        return xcUserExt;
    }

    /**
     * 获取认证类型
     *
     * @return AuthType
     */
    @Override
    public AuthType getAuthType() {
        return AuthType.WX;
    }

    /**
     * 微信扫码登录
     *
     * @param code 授权码
     * @return 用户信息
     */
    @Override
    public XcUser wxAuth(String code) {
        // 1. 申请令牌，获取响应结果
        WeChatTokenOAuthResponse response = getAccess_token(code);
        // 2. 取出令牌和用户的openid
        String access_token = response.getAccess_token();
        String openid = response.getOpenid();
        // 3. 携带令牌查询用户信息
        WeChatUserInfoResponse userinfo = getUserinfo(access_token, openid);
        // 4. 保存用户信息到数据库
        WxAuthServiceImpl currentProxy = (WxAuthServiceImpl) AopContext.currentProxy();
        return currentProxy.addWxUser(userinfo);
    }

    /**
     * 保存用户信息到数据库
     *
     * @param userInfo 用户信息
     * @return 用户信息
     */
    @Transactional
    public XcUser addWxUser(WeChatUserInfoResponse userInfo) {
        String unionid = userInfo.getUnionid();
        String nickname = userInfo.getNickname();
        // 1. 根据unionid查询用户信息，如果存在说明注册过，直接组装username部分
        XcUser xcUser = xcUserMapper.selectOne(new LambdaQueryWrapper<XcUser>().eq(XcUser::getWxUnionId, unionid));
        if (xcUser != null) return xcUser;
        // 2. 否则，就要先完成注册操作，向数据库新增记录
        xcUser = new XcUser();
        String userId = UUID.randomUUID().toString();
        xcUser.setId(userId)// 主键
                .setUsername(unionid)
                .setPassword(passwordEncoder.encode(unionid))
                .setWxUnionId(unionid)
                .setNickName(nickname)
                .setName(nickname)
                .setUserType(UserTypeConstant.USER_TYPE_STUDENT) // 学生类型
                .setStatus("1") // 用户状态
                .setSex(userInfo.getSex() == 0 ? "0" : "1")
                .setUserPic(userInfo.getHeadimgurl())
                .setCreateTime(LocalDateTime.now())
                .setUpdateTime(LocalDateTime.now());
        // 2.1. 向用户表中插入用户信息
        xcUserMapper.insert(xcUser);
        // 2.2. 向用户角色关系表新增记录
        XcUserRole xcUserRole = new XcUserRole();
        xcUserRole.setId(UUID.randomUUID().toString())
                .setUserId(userId)
                .setRoleId(RoleIdConstant.ROLE_ID_STUDENT) // 学生角色
                .setCreateTime(LocalDateTime.now());
        xcUserRoleMapper.insert(xcUserRole);
        // 3. 返回用户信息
        return xcUser;
    }

    /**
     * <a href="https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code">携带授权码申请令牌</a>
     * 返回结果：
     * {
     * "access_token":"ACCESS_TOKEN",
     * "expires_in":7200,
     * "refresh_token":"REFRESH_TOKEN",
     * "openid":"OPENID",
     * "scope":"SCOPE",
     * "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
     * }
     *
     * @param code 授权码
     * @return 响应
     */
    private WeChatTokenOAuthResponse getAccess_token(String code) {
        // 1. 拼接url
        String url_template = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code";
        String url = String.format(url_template, appid, secret, code);
        // 2. 远程调用此url
        ResponseEntity<String> exchange = restTemplate.exchange(url, HttpMethod.POST, null, String.class);
        // 3. 获取响应的结果并转成指定类型
        return JSON.parseObject(exchange.getBody(), WeChatTokenOAuthResponse.class);
    }

    /**
     * <a href="https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID">携带令牌查询用户信息</a>
     * <br>响应结果：<br>
     * {
     * "openid":"OPENID",
     * "nickname":"NICKNAME",
     * "sex":1,
     * "province":"PROVINCE",
     * "city":"CITY",
     * "country":"COUNTRY",
     * "headimgurl": "<a href="https://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/0">url</a>",
     * "privilege":[
     * "PRIVILEGE1",
     * "PRIVILEGE2"
     * ],
     * "unionid": " o6_bmasdasdsad6_2sgVt7hMZOPfL"
     * }
     *
     * @param access_token 微信服务器下达的令牌
     * @param openid       微信用户的openid
     * @return 用户信息
     */
    private WeChatUserInfoResponse getUserinfo(String access_token, String openid) {
        // 1. 构建url请求，并发送
        String url_template = "https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s";
        String url = String.format(url_template, access_token, openid);
        ResponseEntity<String> exchange = restTemplate.exchange(url, HttpMethod.GET, null, String.class);
        // 2. 获取响应的结果
        String result = new String(Objects.requireNonNull(exchange.getBody()).getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
        // 将result转成map
        return JSON.parseObject(result, WeChatUserInfoResponse.class);
    }

    /**
     * 获取token时微信开放平台返回的响应
     */
    @Data // 提供所有字段的getter和setter方法，以及toString、equals、hashCode等方法
    @NoArgsConstructor // 提供无参构造函数
    @AllArgsConstructor // 提供全参构造函数
    static class WeChatTokenOAuthResponse {

        /**
         * 令牌
         */
        private String access_token;

        /**
         * 有效时间，单位：秒
         */
        private long expires_in;

        /**
         * 刷新令牌
         */
        private String refresh_token;

        /**
         * 用户openid
         */
        private String openid;

        /**
         * 授权范围
         */
        private String scope;

        /**
         * 用户unionid
         */
        private String unionid;
    }

    /**
     * 获取用户信息时微信开放平台返回的响应
     */
    @Data // 提供所有字段的getter和setter方法，以及toString、equals、hashCode等方法
    @NoArgsConstructor // 提供无参构造函数
    @AllArgsConstructor // 提供全参构造函数
    public static class WeChatUserInfoResponse {

        /**
         * 用户openid
         */
        private String openid;

        /**
         * 用户昵称
         */
        private String nickname;

        /**
         * 用户性别
         */
        private int sex;

        /**
         * 省份
         */
        private String province;

        /**
         * 城市
         */
        private String city;

        /**
         * 国家
         */
        private String country;

        /**
         * 用户头像
         */
        private String headimgurl;

        /**
         * 用户特权
         */
        private List<String> privilege;

        /**
         * 用户unionid
         */
        private String unionid;
    }
}
